/* vim: set expandtab shiftwidth=4: -*- mode: c++; c-basic-offset: 4; tab-width: 8; indent-tabs-mode: nil -*-
qRPG
An RPG written to test Qt's Graphics View framework
Copyright (C) 2006 Wesley Crossman
Email: wesley@crossmans.net

You can redistribute and/or modify this software under the terms of the GNU
General Public License as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this
program; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
Suite 330, Boston, MA 02111-1307 USA */

#include "BoardCharacter.h"
#include "GameBoardScene.h"
#include "BoardEntry.h"
#include "qmath.h"
#include "itemeffects.h"
#include "GameBoard.h"
#include <QGraphicsView>
#include <QGraphicsItem>
#include <QKeyEvent>
#include <QPainter>
#include <QApplication>
#include <cmath>
#include <cassert>
#include <cstdlib>
#include <ctime>

class LightTileGenerator {
protected:
    DynamicArray < QPixmap * > tiles;
    DynamicArray < bool > tileBlank;
    int tileWH;
public:
    LightTileGenerator(qreal radius, int tileSize);
    QPixmap getTile(int tileX, int tileY);
    int getTileRadius();
};

LightTileGenerator::LightTileGenerator(qreal radius, int tileSize) {
    tileWH = int(std::ceil(radius*2.0)) + 1;
    int phySize = tileWH * tileSize;
    QImage image(phySize, phySize, QImage::Format_ARGB32_Premultiplied);
    QPainter p(&image);
    p.setRenderHint(QPainter::Antialiasing);
    QRadialGradient gradient(phySize/2.0, phySize/2.0, radius*tileSize);
    gradient.setColorAt(.95, QColor(0, 0, 0, 0));
    gradient.setColorAt(1.0, QColor(0, 0, 0, 255));
    QBrush brush(gradient);
    p.fillRect(0, 0, phySize, phySize, brush);
    p.end();
    tiles.setSize(tileWH, tileWH);
    tileBlank.setSize(tileWH, tileWH);
    for (int x=0;x<tileWH;++x) {
        for (int y=0;y<tileWH;++y) {
            QImage tile = image.copy(x*tileSize, y*tileSize, tileSize, tileSize);
            bool blank = true;
            for (int x2=0;x2<tileSize;++x2) {
                for (int y2=0;y2<tileSize;++y2) {
                    int alpha = qAlpha(tile.pixel(x2, y2));
                    if (alpha) blank = false;
                    if  (alpha == qAlpha(image.pixel(0, 0))) {
                        tile.setPixel(x2, y2, qRgba(0, 0, 0, 255));
                    }
                }
            }
            tileBlank.setEntry(x, y, blank);
            /*
            QPainter p(&tile);
            p.setPen(Qt::blue);
            p.drawRect(0, 0, tileSize-1, tileSize-1);
            p.setFont(QFont("sans", 6));
            p.drawText(0, 10, QString("%1 x %2").arg(x).arg(y));
            p.end();
            */
            tiles.setEntry(x, y, (!blank) ? new QPixmap(QPixmap::fromImage(tile)) : new QPixmap());
        }
    }
}

QPixmap LightTileGenerator::getTile(int tileX, int tileY) {
    return *tiles.getEntry(tileX+getTileRadius(), tileY+getTileRadius());
}

int LightTileGenerator::getTileRadius() {
    return tileWH/2;
}

GameItem::GameItem(int x, int y, GameBoardScene *scene) {
    this->x = x;
    this->y = y;
    this->scene = scene;
    item = new QGraphicsPixmapItem(NULL, scene);
    item->setPos(scene->toPhysical(x, y));
    item->setZValue(scene->getLayerIndex("walls")*10+1);
    scene->registerItem(this);
}

GameItem::~GameItem() {
    getOut();
    scene->unregisterItem(this);
    delete item;
}

void GameItem::setX(int value) {
    x = value;
    item->setPos(scene->toPhysical(x, y));
}

void GameItem::setY(int value) {
    y = value;
    item->setPos(scene->toPhysical(x, y));
}

QStringList GameItem::getFunctions() {
    return QStringList();
}

bool GameItem::callFunction(GameCharacter *, QString) {
    assert(false);
}

void GameItem::loseItem(GameItem *item) {
    children.removeAll(item);
    item->setParent(NULL);
}

void GameItem::loseAll() {
    foreach (GameItem *child, children) {
        loseItem(child);
    }
}

void GameItem::getOut() {
    if (parent) parent->removeChildFromList(this);
    setParent(NULL);
}

void GameItem::getIntoItem(GameItem *newParent) {
    getOut();
    if (newParent) newParent->addChildToList(this);
    setParent(newParent);
}

void GameItem::addChildToList(GameItem *item) {
    assert(!children.contains(item));
    children.append(item);
    emit contentsChanged();
}

void GameItem::removeChildFromList(GameItem *item) {
    assert(children.contains(item));
    children.removeAll(item);
    emit contentsChanged();
}

void GameItem::setParent(GameItem *newParent) {
    if (parent) setPos(parent->getPos());
    item->setVisible(!newParent);
    parent = newParent;
}

Potion::Potion(int x, int y, GameBoardScene *scene) : GameItem(x, y, scene) {
}

QStringList Potion::getFunctions() {
    return QStringList() << "Quaff" << "Throw";
}

bool Potion::callFunction(GameCharacter *, QString) {
    assert(false);
}

QStringList Scroll::scrollText;

Scroll::Scroll(int x, int y, GameBoardScene *scene) : GameItem(x, y, scene) {
    stage = 0;
    caller = NULL;
    QFile file(":/data/ipsum.txt");
    file.open(QFile::ReadOnly);
    QTextStream out(&file);
    while (true) {
        QString read = out.readLine();
        if (read.isNull()) break;
        ////for spell overlay
        //scrollText.append(read);
        
        //for spell status bar
        QStringList sentences = read.split(".");
        foreach (QString sentence, sentences) {
            if (sentence.length() > 10) {
                scrollText.append(sentence.trimmed() + ". ");
            }
        }
    }
}

QStringList Scroll::getFunctions() {
    return QStringList() << "Read";
}

bool Scroll::callFunction(GameCharacter *character, QString command) {
    caller = character;
    if (command == "Read") {
        /* //overlays spell over player -- text in constructor needs to be uncommented too
        QGraphicsTextItem *ti = new QGraphicsTextItem(NULL, scene);
        ti->setPlainText(scrollText[0]);
        ti->setTextWidth(160);
        ti->setZValue(scene->getLayerIndex("shadow")*10+1);
        ti->setDefaultTextColor(QColor(0, 255, 0, 128));
        ti->setFont(QFont("serif", 4));
        QPointF mePos = scene->getMe()->item->scenePos();
        QPointF adjustedPos = mePos + scene->getMe()->item->boundingRect().size()/2.0;
        QPointF textPos = adjustedPos - ti->boundingRect().size()/2.0;
        ti->setPos(textPos);*/
        
        //set spell text in status bar
        QString text;
        while (text.size() < 90) {
            QString sentence = scrollText[rand() % scrollText.size()];
            if (!text.contains(sentence)) text += sentence;
        }
        scene->showMessage(text, 2000);
        QTimer::singleShot(2000, this, SLOT(finish()));
        return true;
    } else {
        assert(false);
        return false;
    }
}

void Scroll::finish() {
    stage += 1;
    if (stage == 1) {
        QPointF physical = scene->toPhysical(caller->getPos())
                + QSizeF(scene->getTileSize(), scene->getTileSize())/2;
        Flash *flash = new Flash(physical.x(), physical.y(), NULL, scene);
        connect(flash, SIGNAL(destroyed(QObject *)), SLOT(finish()));
        scene->showMessage("The scroll crumbles.", 1500);
    } else if (stage == 2) {
        deleteLater();
        caller = NULL;
    }
}

Bag::Bag(int x, int y, GameBoardScene *scene) : GameItem(x, y, scene) {
}

QStringList Bag::getFunctions() {
    return QStringList() << "Put In" << "Take Out" << "Empty";
}

bool Bag::callFunction(GameCharacter *, QString) {
    assert(false);
}

GameCharacter::GameCharacter(int x, int y, GameBoardScene *scene) : GameItem(x, y, scene) {
    item->setZValue(scene->getLayerIndex("walls")*10+2);
}

void GameCharacter::ensureVisible() {
    foreach (QGraphicsView *view, scene->views()) {
        GameBoard *board = dynamic_cast < GameBoard * >(view);
        QRectF sRect = view->sceneRect(), iRect = item->sceneBoundingRect();
        QPointF iTL = scene->toPhysical(x, y);
        QPointF iCenter = QPointF(iTL.x() + iRect.width()/2, iTL.y() + iRect.height()/2);
        if (qApp->isLeftToRight() && board->matrix().isIdentity()) {
            board->centerOnX(iCenter.x(), .2);
            board->centerOnY(iCenter.y(), .2);
        } else {
            //I use the safer, inferior method when conditions exist that may cause problems.
            item->ensureVisible(QRect(), 192, 192);
        }
    }
}

bool GameCharacter::isTileClear(int x, int y) {
    int walls = scene->getLayerIndex("walls");
    if (!scene->array.getEntry(x, y)->items[walls]->pixmap().isNull()) {
        return false;
    }
    foreach (GameItem *item, scene->getAtPosition(x, y)) {
        if (item->isBlocking()) {return false;}
    }
    return true;
}

bool GameCharacter::isTileFightable(int x, int y) {
    return false;
}

bool GameCharacter::isValidPath(int x2, int y2) {
    QSize bs = scene->getBoardSize();
    if (x2 < 0 || y2 < 0 || x2 >= bs.width() || y2 >= bs.height()) return false;
    if (x == x2 || y == y2) return isTileClear(x2, y2); //not diagonal
    return isTileClear(x, y2) && isTileClear(x2, y) && isTileClear(x2, y2);
}

Enemy::Enemy(int x, int y, GameBoardScene *scene) : GameCharacter(x, y, scene) {
    srand((unsigned)time(0));
}

void Enemy::tick() {
    QPoint newPos = getPos();
    for (int a=0;a<3;++a) {
        int dir = (std::rand() % 2) ? -1 : 1;
        if (std::rand() % 2) {
            newPos.setX(x + dir);
        } else {
            newPos.setY(y + dir);
        }
        if (isValidPath(newPos)) {
            ItemMover::instance(scene)->move(this, getPos(), newPos, 3);
            x = newPos.x();
            y = newPos.y();
            return;
        }
    }
}

Hero::Hero(int x, int y, GameBoardScene *scene) : GameCharacter(x, y, scene) {
    item->setZValue(scene->getLayerIndex("walls")*10+2);
    qDebug() << "Generating lighting tiles...";
    lightTiles = new LightTileGenerator(5, scene->getTileSize());
    QImage black(scene->getTileSize(), scene->getTileSize(), QImage::Format_RGB32);
    black.fill(Qt::black);
    blackTile = new QPixmap(QPixmap::fromImage(black));
    updateLighting();
}

void Hero::keyPressed(QKeyEvent *e) {
    if (e->key() == Qt::Key_5) {
        emit moved();
        return;
    }
    int xnew = x, ynew = y;
    QPoint oldPos(x, y);
    switch (e->key()) {
    case Qt::Key_1: case Qt::Key_4: case Qt::Key_7: --xnew; break;
    case Qt::Key_3: case Qt::Key_6: case Qt::Key_9: ++xnew; break;
    }
    switch (e->key()) {
    case Qt::Key_7: case Qt::Key_8: case Qt::Key_9: --ynew; break;
    case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: ++ynew; break;
    }
    bool alreadyMoving = ItemMover::instance(scene)->isMoving(this);
    if (alreadyMoving || (x == xnew && y == ynew)) {
    } else if (isValidPath(xnew, ynew) ) {
        int walls = scene->getLayerIndex("walls");
        x = xnew;
        y = ynew;
        QPoint newPos(x, y);
        ItemMover::instance(scene)->move(this, oldPos, newPos, 3);
        updateLighting();
        int delay = ItemMover::instance(scene)->getTimer()->interval()*3;
        QTimer::singleShot(delay, this, SIGNAL(moved()));
        ensureVisible();
    } else if (isTileFightable(xnew, ynew)) {
        
    }
}

void Hero::updateLighting() {
    int radius = lightTiles->getTileRadius();
    int layerIndex = scene->getLayerIndex("shadow");
    if (layerIndex == -1) return; //no shadow layer
    for (int x2=0;x2<scene->getBoardSize().width();++x2) {
        for (int y2=0;y2<scene->getBoardSize().height();++y2) {
            BoardEntry *boardEntry = scene->array.getEntry(x2, y2);
            if (x2 >= x-radius && x2 <= x+radius && y2 >= y-radius && y2 <= y+radius) {
                boardEntry->items[layerIndex]->setPixmap(lightTiles->getTile(x2-x, y2-y));
            } else {
                if (boardEntry->items[layerIndex]->pixmap().serialNumber() !=
                            blackTile->serialNumber()) {
                    boardEntry->items[layerIndex]->setPixmap(*blackTile);
                }
            }
        }
    }
}

